//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: stdif.c 757 2007-05-10 08:19:31Z roger $
//

/* FILE stdif.c */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <dirent.h>
#include <string.h>
#include <dlfcn.h>
#include <iconv.h>
#include <stdio.h>

#include <standard_im.h>
#include <unihan_im.h>

#include "stdif.h"
#include "IMM.h"

const char charset[] = "UTF-8";

static StdIFIMList *stdif_im_list = NULL;
static IMInfo *stdif_get_im_list(void);
static int stdif_init(UnihanIM *);
static int stdif_done(UnihanIM *);
static int stdif_create_session(UnihanIM *);
static int stdif_destroy_session(UnihanIM *);
static int stdif_handle_event(UnihanIM *, IMEvent *);
static int handle_key_event(UnihanIM *uim, IMKeyEvent *kev);
static int handle_focus_event(UnihanIM *uim, IMFocusEvent *fev);
static int handle_config_change_event(UnihanIM *uim, IMConfigChangeEvent *ccev);
static int handle_switch_event(UnihanIM *uim, IMSwitchEvent *sev);
static int stdif_register_im(char *);

int
unihan_im_module_init(void)
{
#if DEBUG
	fprintf(stderr, "STD IF : unihan im module init\n");
#endif
	return TRUE;
}

void
unihan_im_module_done(void)
{
#if DEBUG
	fprintf(stderr, "STD IF : unihan im module done\n");
#endif
	/* FIXME : free stdif_im_list */
	StdIFIMList *stdif_im = stdif_im_list;
	while (stdif_im) {
		stdif_im_list = stdif_im->next;

		//if (stdif_im->ime)
		//	free(stdif_im->ime);
		if (stdif_im->imm)
			free(stdif_im->imm);
		if (stdif_im->property_list) {
			if (stdif_im->property_list->properties)
				free(stdif_im->property_list->properties);
			free(stdif_im->property_list);
		}

		if (stdif_im->im) {				//info in InputMethod is const
			/*if (stdif_im->im->uuid)			//warning ...
				free(stdif_im->im->uuid);
			if (stdif_im->im->encoding)
				free(stdif_im->im->encoding);
			if (stdif_im->im->name)
				free(stdif_im->im->name);
			if (stdif_im->im->icon)
				free(stdif_im->im->name);
			if (stdif_im->im->locales)
				free(stdif_im->im->locales);
			if (stdif_im->im->help)
				free(stdif_im->im->help);
			if (stdif_im->im->copyright)
				free(stdif_im->im->copyright);
			if (stdif_im->im->version)
				free(stdif_im->im->version);*/
			free(stdif_im->im);
		}

		dlclose(stdif_im->control->dlh);
		free(stdif_im->control->fullpath);

		stdif_im = stdif_im->next;
	}
	stdif_im_list = NULL;
	return;
}

int
unihan_im_module_get_info(UnihanIMModuleArgList *args)
{
#if DEBUG
	fprintf(stderr, "STD IF : unihan im module get info\n");
#endif
	if (!args)
		return FALSE;
	while (args->id) {
		switch (args->id) {
			case UNIHAN_IM_MODULE_ID:
				args->value = strdup("stdif");
				break;
			case UNIHAN_IM_MODULE_NAME:
				args->value = strdup("Chinese Standard IM IF");
				break;
			case UNIHAN_IM_MODULE_VERSION:
				args->value = strdup("1.0");
				break;
			case UNIHAN_IM_MODULE_IM_LIST:
				args->value = stdif_get_im_list();
				break;
			
		}
		args++;
	}
	return TRUE;
}

static IMInfo *
stdif_get_im_list()
{
	IMInfo *im_list = NULL;
	DIR *dir;
	char *path = STD_IM_MODULES_DIR;

	union {
		struct dirent d;
		char b[sizeof(struct dirent) + NAME_MAX + 1];
	} u;

	struct dirent *res = NULL;
	IMMethods *imm = NULL;
	IMInfo *im = NULL;
	StdIFIMList *stdif_im = NULL;

	dir = opendir(STD_IM_MODULES_DIR);
	if (!dir) {
		// FIXME: error handling
#if DEBUG
		fprintf(stderr, "STD IF : access standard im dir failed\n");
#endif
		return NULL;
	}

	while (readdir_r(dir, &u.d, &res) == 0 && res != NULL) {
		char *ext = strrchr(res->d_name, (int)'.');
		if (ext && !strcmp(".so", ext)) {
			//char *tmp;
			char *fullpath;
			StdIFIMList *std_im = NULL;
			fullpath = (char*)malloc(strlen(path)+strlen(res->d_name)+2);
			sprintf(fullpath, "%s/%s", path, res->d_name);

			if ( !stdif_register_im(fullpath) ) {
#if DEBUG
				fprintf(stderr, "STD IF : %s is not a standard im\n", fullpath);
#endif
				continue;
			}

		}
	}
	closedir(dir);

	stdif_im = stdif_im_list;
	while (stdif_im) {
		imm = (IMMethods *)malloc(sizeof(IMMethods));
		memset(imm, 0, sizeof(IMMethods));

		imm->init = stdif_init;
		imm->done = stdif_done;
		imm->create_session = stdif_create_session;
		imm->destroy_session = stdif_destroy_session;
		imm->handle_event = stdif_handle_event;

		im = (IMInfo *)malloc(sizeof(IMInfo));
		memset(im, 0, sizeof(IMInfo));

		im->id = strdup(stdif_im->im->uuid);
		im->name = strdup(stdif_im->im->name);
		im->version = strdup(stdif_im->im->version);
		im->methodTable = imm;
		im->imUserData = strdup(stdif_im->control->fullpath);
		im->next = im_list;

		im_list = im;

		stdif_im = stdif_im->next;
	}

	return im_list;
}

static int
stdif_init(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "STD IF : init im %s\n", uim->id);
#endif
	StdifDataPerDesktop *dd;
	char *id = uim->id;
	StdIFIMList *stdif_im = stdif_im_list;
	const char *encoding;
#if DEBUG
	char *locale;
	int err;

	err = uim->m->locale_get(uim, &locale);
	if (err == 0)
		fprintf(stderr, "STD IF : user's locale is %s\n", locale);
#endif

	while (stdif_im) {
		if (strcmp(id, stdif_im->im->uuid) == 0)
			break;
		stdif_im = stdif_im->next;
	}
	if (!stdif_im)
		return FALSE;

	dd = (StdifDataPerDesktop *)malloc(sizeof(StdifDataPerDesktop));
	memset(dd, 0, sizeof(StdifDataPerDesktop));

	//FIXME : iconv
	encoding = stdif_im->im->encoding;
	dd->iconv = iconv_open("UTF-8", encoding);

	uim->desktopUserData = dd;

	UIM = uim;

	(* stdif_im->ime->initialize)();

	return TRUE;
}

static int
stdif_done(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "STD IF : im %s done\n", uim->id);
#endif
	char *id = uim->id;
	StdIFIMList *stdif_im = stdif_im_list;
	StdifDataPerDesktop *dd = uim->desktopUserData;

	while (stdif_im) {
		if (strcmp(id, stdif_im->im->uuid) == 0)
			break;
		stdif_im = stdif_im->next;
	}
	if (!stdif_im)
		return FALSE;

	(* stdif_im->ime->finalize)();

	iconv_close(dd->iconv);
	free(dd);
	uim->desktopUserData = NULL;

	UIM = NULL;
	return TRUE;
}

static int
stdif_create_session(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "STD IF : create session\n");
#endif
	StdifDataPerSession *ds = NULL;
	char *id = uim->id;
	StdIFIMList *stdif_im = stdif_im_list;

	while (stdif_im) {
		if (strcmp(id, stdif_im->im->uuid) == 0)
			break;
		stdif_im = stdif_im->next;
	}
	if (!stdif_im)
		return FALSE;

	ds = (StdifDataPerSession *)malloc(sizeof(StdifDataPerSession));
	memset (ds, 0, sizeof(StdifDataPerSession));

	ds->curr_im = stdif_im;

	ds->ic = (IME_InputContext *)malloc(sizeof(IME_InputContext));
	memset(ds->ic, 0, sizeof(IME_InputContext));

	ds->ic->uuid = stdif_im->im->uuid;
	ds->ic->charset = charset;

	if (!(* stdif_im->ime->create_input_context)(ds->ic))
		return FALSE;

	//ds->preedit = (IME_String *)malloc(sizeof(IME_String));
	//memset(ds->preedit, 0, sizeof(IME_String));

	//ds->candidates_list = (IME_CandidatesList *)malloc(sizeof(IME_CandidatesList));
	//memset(ds->candidates_list, 0, sizeof(IME_CandidatesList));

	uim->sessionUserData = ds;
	UIM = uim;
	return TRUE;
}

static int
stdif_destroy_session(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "STD IF : destroy session\n");
#endif
	StdifDataPerSession *ds = uim->sessionUserData;

	if (ds->ic) {
		(* ds->curr_im->ime->destroy_input_context)(ds->ic);
		free(ds->ic);
	}

	uim->m->preedit_erase(uim);
	uim->m->lookup_done(uim);
	free(ds);
	uim->sessionUserData = NULL;
	return TRUE;
}

static int
stdif_handle_event(UnihanIM *uim, IMEvent *ev)
{
#if DEBUG
	fprintf(stderr, "STDIF : stdif_handle_event()\n");
#endif

	switch (ev->type) {
		case IM_KEY:
			return handle_key_event(uim, (IMKeyEvent*)ev);
		case IM_FOCUS:
			return handle_focus_event(uim, (IMFocusEvent*)ev);
		case IM_CONFIG_CHANGE:
			return handle_config_change_event(uim, (IMConfigChangeEvent*)ev);
		case IM_SWITCH:
			return handle_switch_event(uim, (IMSwitchEvent*)ev);
		case IM_NOTHING:
		default:
			break;
	}

	return FALSE;
}

static int
handle_key_event(UnihanIM *uim, IMKeyEvent *kev)
{
	UIM = uim;
#if DEBUG
	fprintf(stderr, "STD IF : handle_key_event()\n");
#endif
	StdifDataPerSession *ds = (StdifDataPerSession *)uim->sessionUserData;
	IME_KeyEvent ime_kev;
	IME_Bool ret;

	ime_kev.code = kev->keyCode;
	ime_kev.ascii = kev->keyChar;
	ime_kev.mask = kev->modifier;

	ret = (*ds->curr_im->ime->process_key_event)(ds->ic, &ime_kev);
}

static int
handle_focus_event(UnihanIM *uim, IMFocusEvent *fev)
{
#if DEBUG
	fprintf(stderr, "STD IF : handle_focus_event()\n");
#endif
	StdifDataPerSession *ds = (StdifDataPerSession *)uim->sessionUserData;
	if (fev->focused == TRUE)
		(* ds->curr_im->ime->focus_in)(ds->ic);
	else
		(* ds->curr_im->ime->focus_out)(ds->ic);
	return TRUE;
}

static int
handle_config_change_event(UnihanIM *uim, IMConfigChangeEvent *ccev)
{
#if DEBUG
	fprintf(stderr, "STD IF : handle_config_change_evnet()\n");
#endif
	return TRUE;
}

static int
handle_switch_event(UnihanIM *uim, IMSwitchEvent *sev)
{
#if DEBUG
	fprintf(stderr, "STD IF : handle_switch_event()\n");
#endif
	return TRUE;
}

static int
stdif_register_im(char *im_so_path)
{
	StdIFIMList *stdif_im = NULL;
	IMModuleControl *control = NULL;
	IME_Functions *ime;
	IMM_Functions *imm;
	int number, i;
	
#if DEBUG
	fprintf(stderr, "STD IF : register im %s\n", im_so_path);
#endif

	control = (IMModuleControl *)malloc(sizeof(IMModuleControl));
	memset(control, 0, sizeof(control));

	control->dlh = dlopen(im_so_path, RTLD_LAZY);
	if (dlerror() != NULL) {
#if DEBUG
		fprintf(stderr, "STD IF : cannot dlopen module %s\n", im_so_path);
#endif
		free(control);
		return FALSE;
	}
	control->ime_register = dlsym(control->dlh, "ime_register_module");
	if (dlerror() != NULL) {
#if DEBUG
		fprintf(stderr, "STD IF : cannot dlsym ime_register_modle()\n");
#endif
		dlclose(control->dlh);
		free(control);
		return FALSE;
	}
	control->fullpath = im_so_path;

	imm = (IMM_Functions *)malloc(sizeof(IMM_Functions));
	memset(imm, 0, sizeof(IMM_Functions));
	IMM_set_functions(&imm);

	if (!(*control->ime_register)(imm, ime)) {
#if DEBUG
		fprintf(stderr, "STD IF : module %s register failed\n", im_so_path);
#endif
		dlclose(control->dlh);
		free(control);
		return FALSE;
	}

	number = (*ime->get_number_of_input_methods)();

	for (i=0; i<number; i++) {
		if (!stdif_im_list) {
			stdif_im = stdif_im_list = (StdIFIMList *)malloc(sizeof(StdIFIMList));
			memset(stdif_im_list, 0, sizeof(StdIFIMList));
		} else {
			stdif_im = (StdIFIMList *)malloc(sizeof(StdIFIMList));
			memset(stdif_im, 0, sizeof(StdIFIMList));
			stdif_im->next = stdif_im_list;
			stdif_im_list->prev = stdif_im;
			stdif_im_list = stdif_im;
		}
		stdif_im->ime = ime;
		stdif_im->imm = imm;
		stdif_im->control = control;

		stdif_im->im = (IME_InputMethod *)malloc(sizeof(IME_InputMethod));
		memset(stdif_im->im, 0, sizeof(IME_InputMethod));
		(*ime->get_input_method_info)(i, stdif_im->im);
	}
	return TRUE;
}


// vi:ts=4:nowrap:cin:expandtab
